/**
 * LCD1602（Liquid Crystal Display）液晶显示屏
 * 是一种字符型液晶显示模块，可以显示ASCII码的标准字符和其它的一些内置特殊字符，还可以有8个自定义字符
 * 显示容量：16×2个字符，每个字符占5*7点阵 (实际5*8)
 *
 * 引脚及应用电路:
 * 引脚     功能
 *  VSS     地
 *  VDD     电源正极（4.5~5.5V）
 *  VO      对比度调节电压
 *  RS      数据/指令选择，1为数据，0为指令
 *  RW      读/写选择，1为读，0为写
 *  E       使能，1为数据有效，下降沿执行命令
 *  D0~D7   数据输入/输出
 *  A       背光灯电源正极
 *  K       背光灯电源负极
 *
 * AC（光标位置）
 * CGRAM+CGROM（字模库）     : Character Generator, CGRAM可自定义8个字符. CGROM只读
 * DDRAM（数据显示区）（40*2）: 数据写入到这里面. 第1行地址: 00H~27H, 第2行地址: 40H~67H
 * 
 * LCD1602字符型液晶显示模块指令集:
 *   指令    RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0  功能                                  执行时间
 * 1.清屏    0  0   0   0   0   0   0   0   0   1    清除 DDRAM 和AC 值，光标复位 (清屏)    1.64us
 * 2.归位    0  0   0   0   0   0   0   0   1   *    AC=0光标复位、DDRAM 内容不变。(复位)   1.64us
 * 3.输入    0  0   0   0   0   0   0   1   1   1    数据读、写操作后，AC++:画面平移。      40us
 *   方式设置                           1   1   0    数据读、写操作后，AC++;画面不动。
 *                                      1   0   1    数据读、写操作后，AC--:画面平移。
 *                                      1   0   0    数据读、写操作后，AC--:画面不动。
 * 4.显示    0  0   0   0   0   0   1   0   0   0    显示关，光标关，闪烁关。               40us
 *   开关控制                       1   0   0   1    显示关，光标关，闪烁开。
 *                                  1   0   1   0    显示关，光标开，闪烁关。
 *                                  1   0   1   1    显示关，光标开，闪烁开。
 *                                  1   1   0   0    显示开，光标关，闪烁关。
 *                                  1   1   0   1    显示开，光标关，闪烁开。
 *                                  1   1   1   0    显示开，光标开，闪烁关。
 *                                  1   1   1   1    显示开，光标开，闪烁开。
 * 5.光标、  0  0   0   0   0   1   0   0   *   *    光标向左平移一个字符位，AC--。         40us
 * 画面位移                     1   0   1   *   *    光标向右平移一个字符位，AC++。
 *                              1   1   0   *   *    画面向左平移一个字符位，但光标不动。移屏
 *                              1   1   1   *   *    画面向右平移一个字符位，但光标不动。移屏
 * 6.功能    0  0   0   0   1   0   0   0   *   *    四位数据接口，一行显示，5x7 点阵       40us
 *   设置                   1   0   0   1   *   *    四位数据接口，一行显示，5x10 点阵
 *                          1   0   1   0   *   *    四位数据接口，两行显示，5x7 点阵
 *                          1   0   1   1   *   *    四位数据接口，两行显示，5x10 点阵
 *                          1   1   0   0   *   *    八位数据接口，一行显示，5x7 点阵
 *                          1   1   0   1   *   *    八位数据接口，一行显示，5x10 点阵
 *                          1   1   1   0   *   *    八位数据接口，两行显示，5x7 点阵
 *                          1   1   1   1   *   *    八位数据接口，两行显示，5x10 点阵
 * 7.CGRAM   0  0   0   1   A5  A4  A3  A2  A1  A0   设置CGRAM 地址。A5~A0(0~3FH)          40us
 *   地址设置
 * 8.DDRAM   0  0   1   A6  A5  A4  A3  A2  A1  A0   设定下一个要存入数据的 DDRAM 的地址。  40us
 *   地址设置
 * 9.读 BF   0  1   BF  AC6 AC5 AC4 AC3 AC2 AC1 AC0  BF=1:忙;BF=0:准备好。                 40us
 *   及AC值                                          AC 值意义为最近一次地址设置(CGRAM或DDRAM)定义。
 */
#include <REGX52.H>
#include <STDARG.H>
#include <STDIO.H>
#include "Number_Utils.h"

//引脚配置(视频)：
// sbit LCD1602_RS = P2^6;  //液晶数据命令选择端
// sbit LCD1602_RW = P2^5;
// sbit LCD1602_EN = P2^7;  //液晶写数据控制端

//引脚配置(TX-1C):
sbit LCD1602_RS = P3^5;     //数据/指令选择端，1为数据，0为指令
bit LCD1602_RW = 0;         //读/写选择，1为读，0为写. (TX-1C 的RW接地)
sbit LCD1602_EN = P3^4;     //使能，1为数据有效，下降沿执行命令
#define LCD1602_DataPort P0 //D0~D7 数据输入/输出

/**
 * @brief LCD1602延时函数，延时399us(视频是1ms), 用于命令执行时间
 * 手册上写的命令执行时间40us, 但经过实测, 至少368us才行(阈值,按复位键第1字符时显时隐)...
 */
void LCD1602_Delay() {      //@11.0592MHz
    unsigned char i;
    i = 181;
    while (--i);
}

/**
 * @brief  LCD1602写指令
 * @param  Command 要写入的指令
 */
void LCD1602_WriteCommand(unsigned char Command) {
    //数据/指令选择端，1为数据，0为指令
    LCD1602_RS = 0;
    //读/写选择，1为读，0为写. (TX-1C 的RW接地)
    LCD1602_RW = 0;
    LCD1602_DataPort = Command;
    LCD1602_EN = 1;
    LCD1602_Delay();
    LCD1602_EN = 0;
    LCD1602_Delay();
}

/**
 * @brief  LCD1602写数据
 * @param  Data 要写入的数据
 */
void LCD1602_WriteData(unsigned char Data) {
    LCD1602_RS = 1;
    LCD1602_RW = 0;
    LCD1602_DataPort = Data;
    LCD1602_EN = 1;
    LCD1602_Delay();
    LCD1602_EN = 0;
    LCD1602_Delay();
}

/**
 *           RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
 * 1.清屏    0  0   0   0   0   0   0   0   0   1    清除 DDRAM 和AC 值，光标复位 (清屏)    1.64us
 */
void LCD1602_Clear() {
    LCD1602_WriteCommand(0x01);
}

/**
 *           RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
 * 4.显示    0  0   0   0   0   0   1   0   0   0    显示关，光标关，闪烁关。               40us
 *   开关控制                       1   0   0   1    显示关，光标关，闪烁开。
 *                                  1   0   1   0    显示关，光标开，闪烁关。
 *                                  1   0   1   1    显示关，光标开，闪烁开。
 *                                  1   1   0   0    显示开，光标关，闪烁关。
 *                                  1   1   0   1    显示开，光标关，闪烁开。
 *                                  1   1   1   0    显示开，光标开，闪烁关。
 *                                  1   1   1   1    显示开，光标开，闪烁开。
 * @param show 显示开/关
 * @param courseShow 光标开/关
 * @param isTwinkle 闪烁开/关
 */
void LCD1602_DisplaySet(unsigned char show, unsigned char courseShow, unsigned char isTwinkle) {
    LCD1602_WriteCommand(0x08 | (show<<2) | (courseShow<<1) | isTwinkle);
}

/**
 * 移屏
 *           RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
 * 5.光标、  0  0   0   0   0   1   0   0   *   *    光标向左平移一个字符位，AC--。         40us
 * 画面位移                     1   0   1   *   *    光标向右平移一个字符位，AC++。
 *                              1   1   0   *   *    画面向左平移一个字符位，但光标不动。移屏
 *                              1   1   1   *   *    画面向右平移一个字符位，但光标不动。移屏
 * @param isLeft 是否网左移. 1往左, 0往右
 */
void LCD1602_ScreenTranslate(unsigned char isLeft) {
    LCD1602_WriteCommand(0x18 | (!isLeft<<2));
}
/**
  * @brief  LCD1602初始化函数
  * @retval 无
  */
void LCD1602_Init(bit isTx1C) {
    if (isTx1C) {
        //关闭数码管显示, ∵TX-1C的数码管也是P0口
        P2_7 = 0;   //WELA, U2
        P2_6 = 0;   //DULA, U1
    }
    //6.功能设置: 0011 1000, 八位数据接口，两行显示，5*7点阵
    LCD1602_WriteCommand(0x38);
    //4.显示开关控制: 显示开，光标关，闪烁关
    LCD1602_DisplaySet(1, 0, 0);
    //3.输入方式设置: 0000 0110, 数据读写操作后，光标自动加一，画面不动
    LCD1602_WriteCommand(0x06);
    //1.清屏
    LCD1602_Clear();
}

/**
 * DDRAM（数据显示区）（40*2）: 数据写入到这里面. 第1行地址: 00H~27H, 第2行地址: 40H~67H
 * 
 *           RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
 * 8.DDRAM   0  0   1   A6  A5  A4  A3  A2  A1  A0   设定下一个要存入数据的 DDRAM 的地址。  40us
 *   地址设置
 * @brief  LCD1602设置光标位置
 * @param  Line 行位置，范围：1~2
 * @param  Column 列位置，范围：1~16
 * @retval 无
 */
void LCD1602_SetCursor(unsigned char Line, unsigned char Column) {
    if(Line == 1) {
        LCD1602_WriteCommand(0x80 | (Column - 1));
    } else if(Line == 2) {
        LCD1602_WriteCommand(0x80 | (Column - 1 + 0x40));
    }
}

/**
  * @brief  在LCD1602指定位置上显示一个字符
  * @param  Line 行位置，范围：1~2
  * @param  Column 列位置，范围：1~16
  * @param  Char 要显示的字符
  * @retval 无
  */
void LCD1602_ShowChar(unsigned char Line, unsigned char Column, unsigned char Char) {
    LCD1602_SetCursor(Line, Column);
    LCD1602_WriteData(Char);
}

/**
  * @brief  在LCD1602指定位置开始显示所给字符串
  * @param  Line 起始行位置，范围：1~2
  * @param  Column 起始列位置，范围：1~16
  * @param  String 要显示的字符串
  * @retval 无
  */
void LCD1602_ShowString(unsigned char Line, unsigned char Column, char *String) {
    unsigned char i = 0;
    LCD1602_SetCursor(Line, Column);
    while (String[i]) LCD1602_WriteData(String[i++]);
    // {
    //     /* code */
    // }
    
    // for(i=0; String[i] != '\0'; i++) {
    //     LCD1602_WriteData(String[i]);
    // }
}

/**
 * 显示格式化字符串, 注意: 整数默认是unsigned int, 即%bu
 * @param Format 格式化字符串,         示例: "a=%bu, b=%c, c=%f"
 * @param ... 要填入格式化字符串的参数, 示例: 123, 'A', 12.345F
 */
void LCD1602_ShowStringFormat(unsigned char Line, unsigned char Column, char* Format, ...) {
    xdata char Dest[1];
    va_list args;
    va_start(args, Format);
    vsprintf(Dest, Format, args);
    va_end(args);
    LCD1602_ShowString(Line, Column, Dest);
}

/**
 * 显示float, double
 * @param showSigned 是否在 float/double 前显示 "+" 或 "-" 号
 * @param Double float/double
 */
void LCD1602_ShowDouble(unsigned char Line, unsigned char Column, bit showSigned, double Double) {
    if (showSigned) {
        LCD1602_ShowStringFormat(Line, Column, "%+.7g", Double);
    } else LCD1602_ShowStringFormat(Line, Column, "%.7g", Double);
}

/**
  * @brief  在LCD1602指定位置开始显示所给数字
  * @param  Line 起始行位置，范围：1~2
  * @param  Column 起始列位置，范围：1~16
  * @param  Number 要显示的数字，范围：0~4294967295 (2^32-1)
  * @param  Length 要显示数字的长度，范围：1~10 (由于先显示长数字, 再显示短数字时, 最后长出的部分无法覆盖, ∴传入Length固定长度)
  * @retval 无
  */
void LCD1602_ShowNum(unsigned char Line, unsigned char Column, unsigned long int Number, unsigned char Length) {
    //无需Length
    // unsigned char len;
    // unsigned long int divider;
    // len = Number_Len(Number);
    // divider = Number_Pow(10, len - 1);
    // LCD1602_SetCursor(Line, Column);
    // for (; len--; divider /= 10) {
    //     //789:   7=789/100%10   8=789/10%10   9=789/1%10
    //     LCD1602_WriteData(Number / divider % 10 + '0');
    // }
    unsigned char len;
    unsigned long int divider;
    divider = Number_Pow(10, Length - 1);
    LCD1602_SetCursor(Line, Column);
    for(len = Length; len > 0; len--, divider /= 10) {
        LCD1602_WriteData(Number / divider % 10 + '0');
    }
}

/**
  * @brief  在LCD1602指定位置开始以有符号十进制显示所给数字
  * @param  Line 起始行位置，范围：1~2
  * @param  Column 起始列位置，范围：1~16
  * @param  Number 要显示的数字，范围：-2147483648~2147483647
  * @param  Length 要显示数字的长度，范围：1~10
  * @retval 无
  */
void LCD1602_ShowSignedNum(unsigned char Line, unsigned char Column, long int Number, unsigned char Length) {
    //防止-Number 溢出, ∴用unsigned long int
    unsigned long int Number1;
    LCD1602_SetCursor(Line, Column);
    if(Number >= 0) {
        LCD1602_WriteData('+');
        Number1 = Number;
    } else {
        LCD1602_WriteData('-');
        Number1 = -Number;
    }
    LCD1602_ShowNum(Line, ++Column, Number1, Length);
}

/**
  * @brief  在LCD1602指定位置开始以十六进制显示所给数字
  * @param  Line 起始行位置，范围：1~2
  * @param  Column 起始列位置，范围：1~16
  * @param  Number 要显示的数字，范围：0~0xFFFF FFFF
  * @param  Length 要显示数字的长度，范围：1~8
  * @retval 无
  */
void LCD1602_ShowHexNum(unsigned char Line, unsigned char Column, unsigned long int Number, unsigned char Length) {
    //1.格式化输出
    // LCD1602_ShowStringFormat(Line, Column, "%lX", Number);
    //2.视频代码, 需要unsigned char Length(显示几位16进制)
    unsigned char SingleNumber;
    LCD1602_SetCursor(Line, Column);
    for (; Length--;) {
        //0xA8(168):   A=168/16%16   8=168/1%16
        SingleNumber = (Number >> (Length * 4)) % 16;
        if(SingleNumber < 10) {
            LCD1602_WriteData(SingleNumber + '0');
        } else {
            LCD1602_WriteData(SingleNumber - 10 + 'A');
        }
    }
    //3.修改后, 不要Length
    // unsigned char len, SingleNumber;
    // len = Number_Len_Hex(Number);
    // LCD1602_SetCursor(Line, Column);
    // for (; len--;) {
    //     //0xA8(168):   A=168/16%16   8=168/1%16
    //     SingleNumber = (Number >> (len * 4)) % 16;
    //     if(SingleNumber < 10) {
    //         LCD1602_WriteData(SingleNumber + '0');
    //     } else {
    //         LCD1602_WriteData(SingleNumber - 10 + 'A');
    //     }
    // }
}

/**
  * @brief  在LCD1602指定位置开始以二进制显示所给数字
  * @param  Line 起始行位置，范围：1~2
  * @param  Column 起始列位置，范围：1~16
  * @param  Number 要显示的数字，范围：0~1111 1111 1111 1111 1111 1111 1111 1111
  * @retval 无
  */
void LCD1602_ShowBinNum(unsigned char Line, unsigned char Column, unsigned long int Number) {
    //1.视频代码, 需要unsigned char Length(显示几位2进制)
    // unsigned char i;
    // LCD1602_SetCursor(Line, Column);
    // for(i = Length; i > 0; i--) {
    //     LCD1602_WriteData(Number / Number_Pow(2, i-1) % 2 + '0');
    // }
    //2.修改后, 不要Length
    unsigned char len;
    len = Number_Len_Binary(Number);
    LCD1602_SetCursor(Line, Column);
    for (; len--;) {
        //0xAA(10101010):   1=0xAA>>7%2   0=0xAA>>6%2
        LCD1602_WriteData((Number >> len) % 2 + '0');
    }
}
